Übersicht Ipython Notebooks im Data Mining Praktikum
In diesem Versuch sollen Kenntnisse in folgenden Themen vermittelt werden:
Sämtliche Verfahren und Algorithmen werden in Python implementiert.
Die Gesichtserkennung kann mit unterschiedlichen Ansätzen realisiert werden. In diesem Versuch wird ausschließlich der Eigenface-Ansatz vorgestellt. Dieser Ansatz basiert auf der Principal Component Analysis (PCA) und wurde erstmals in M. Turk, A. Pentland; Eigenfaces for Recognition vorgestellt. Die Eigenface-Methode weist eine gute Performance im Fall biometrisch aufgenommener Gesichtsbilder auf.
Bilder mit $C$ Pixeln in der Breite und $R$ Pixeln in der Höhe können als $R \times C$ Matrizen abgespeichert werden. Handelt es sich um ein Schwarz-Weiß- oder Graustufen-Bild, dann wird pro Bild nur eine derartige Matrix benötigt. Der Eintrag in der i.ten Zeile und j.ten Spalte dieser Matrix definiert den Grauwert des entsprechenden Pixels. In Farbbildern werden je nach benutztem Farbraum mehrere Matrizen pro Bild benötigt, wobei jede Matrix einen Farbkanal des Bildes repräsentiert. Für ein RGB-Bild werden z.B. 3 Matrizen für die Farbkanäle Rot, Grün und Blau benötigt. \
Im Folgenden wird von quadratischen Graubildern mit $N \times N$ Pixeln ausgegangen. Wird jedes Pixel als ein Merkmal betrachtet, dann existieren insgesamt $N^2$ Merkmale, das Bild kann auch als ein Punkt im $N^2$-dimensionalen Raum betrachtet werden. Bilder der Auflösung $256 \times 256$ müßten also im $65536$-dimensionalen Raum beschrieben werden. Entsprechend komplex wäre die notwendige Verarbeitung. Ist jedoch bekannt, dass in einer Menge von Bildern jeweils ein gleichartiges Objekt abgebildet ist, z.B. wenn alle Bilder ausschließlich je ein Gesicht enthalten, dann existieren große Abhängigkeiten zwischen diesen Bildern. Geometrisch ausgedrückt bedeutet dies, dass die Punkte, welche die Menge der gleichartigen Bilder beschreiben, nicht gleichmäßig über den $N^2$-dimensionalen Raum verteilt sind, sondern in einen relativ kleinen Unterraum mit $K<<N^2$ Dimensionen nahezu vollständig beschrieben werden können. Jede dieser $K$ Dimensionen beschreibt ein für die Kategorie (z.B. Gesichtsbilder) relevantes Merkmal. Im Fall der Gesichtserkennung werden die relevanten Merkmale auch als Eigenfaces bezeichnet. Jedes Eigenface kann als Bild dargestellt werden, welches ein bestimmtes Gesichtsmerkmal besonders hervorhebt. Jedes individuelle Bild der Kategorie (d.h. jedes Gesicht) kann dann als Linearkombination der $K$ relevanten Merkmale (der $K$ Eigenfaces) beschrieben werden.\
Das Problem besteht nun zunächst darin, aus einer Menge von Bildern der gleichen Kategorie die relevanten Merkmale zu finden. Dieses Problem wird durch die Principal Component Analysis (PCA) gelöst. Die PCA, findet in einer Menge von Bildern der gleichen Kategorie die Hauptachsen, also die Richtungen im $N^2$-dimensionalen Raum, entlang derer die Varianz zwischen den gegebenen Bildern am stärksten ist. Der $N^2$-dimensionale Pixelraum wird dann in einen Raum, der durch die gefundenen Hauptachsen aufgespannt wird, transformiert. In diesem in der Anzahl der Dimensionen stark reduzierten Raum wird dann die Bilderkennung durchgeführt. Der hier skizzierte Ansatz der Eigenfaces für die Gesichtserkennung wurde erstmalig in M. Turk, A. Pentland; Eigenfaces for Recognition beschrieben.
Die Gesichtserkennung besteht aus 2 Phasen. In der Trainingsphase werden die Gesichtsbilder der zu erkennenden Personen eingelesen und für diese mit der PCA der Eigenface-Raum berechnet. In der Erkennungsphase wird ein neu aufgenommenes Bild in den Eigenface-Raum transformiert und dort dem naheliegendsten Bild aus der Trainingsmenge zugeordnet.
Füge das erkannte Bild zur Menge der Trainingsbilder hinzu und führe die Schritte der Trainingsphase durch.
Es werden zunächst $M$ Gesichtsbilder der zu erkennenden Personen eingelesen (von jeder zu erkennenden Personen möglichst mehrere Bilder). Es wird davon ausgegangen, dass jedes der Bilder $C$ Pixel breit und $R$ Pixel hoch ist. Das Bild kann dann als $R \times C$ Matrix dargestellt werden. Im Fall eines Graustufenbildes repräsentieren die Pixelwerte den entsprechenden Grauwert. Nach dem Einlesen werden die Bildmatrizen als eindimensionale Vektoren dargestellt. Für diese Umformung werden die Zeilen jeder Matrix von oben nach unten ausgelesen und hintereinander gereiht. Jedes Bild wird dann durch einen Vektor der Länge $Z=R \cdot C$ repräsentiert und kann als Punkt im Z-dimensionalen Raum dargestellt werden. Die M Bildvektoren werden im folgenden mit $$\Gamma _1, \Gamma_2, \ldots, \Gamma_M$$ bezeichnet.
Im nächsten Schritt wird das Durchschnittsbild berechnet $$ \overline{\Gamma}=\frac{1}{M}\sum_{i=1}^{M}{\Gamma_{i}} $$
Dieses Durchschnittsbild wird von allen Bildern $\Gamma_i$ abgezogen. Die Menge der so gewonnenen Bildrepräsentationen $$ \Phi_i=\Gamma_i - \overline{\Gamma} $$
ist dann mittelwertsfrei. Die Menge $\Phi_1, \Phi_2, \ldots, \Phi_M$ wird dann einer Principal Component Analysis (PCA) (siehe auch J. Maucher; Feature Selection and Extraction) unterzogen. Hierzu werden die mittelwertfreien Bildrepräsentationen $\Phi_i$ in die Spalten einer Matrix geschrieben. Diese Matrix wird im Folgenden mit $X$ bezeichnet. Unter der Annahme, dass die $\Phi_i$ bereits als Spaltenvektoren vorliegen, ist die Matrix $X$ definiert als:
$$ X=\left[ \Phi_1, \Phi_2, \ldots, \Phi_M \right]. $$
Die entsprechende Kovarianzmatrix ist dann $$ CV=X \cdot X^T. $$
Für die PCA müssten als nächstes eigentlich die Eigenvektoren und Eigenwerte der Kovarianzmatrix $CV$ berechnet werden. Für den vorliegenden Fall kann allerdings die hierfür notwendige Berechnung aus Komplexitätsgründen nicht realisiert werden. Man beachte dass die Matrix $CV$ $Z$ Spalten und $Z$ Zeilen enthält ($Z$ ist die Anzahl der Pixel in einem Bild) und für diese $Z$ Eigenvektoren und Eigenwerte existieren. Wie in M. Turk, A. Pentland; Eigenfaces for Recognition beschrieben, existieren im Fall, dass die Anzahl der Bilder $M$ wesentlich kleiner als die Anzahl der Pixel $Z$ ist, nur $M-1$ relevante Eigenvektoren, die Eigenwerte aller anderen Eigenvektoren liegen nahe bei Null. Der in M. Turk, A. Pentland; Eigenfaces for Recognition beschriebene Ansatz geht nun von der $M \times M$ Matrix
$$ X^T \cdot X $$
aus, für welche die Eigenvektoren und Eigenwerte für eine moderate Bildanzahl $M$ gut berechnet werden können. Per Definition gilt für die Eigenvektoren $\mathbf{v}_i$ und Eigenwerte $\mu_i$ dieser Matrix:
$$ X^T \cdot X \cdot \mathbf{v}_i = \mu_i \mathbf{v}_i . $$
Werden beide Seiten dieser Matrix linksseitig mit der Matrix $X$ multipliziert,
$$ X \cdot X^T \cdot X \cdot \mathbf{v}_i = \mu_i X \mathbf{v}_i, $$ dann ist daraus zu erkennen, dass die $M$ Vektoren $$ \mathbf{u}_i=X \mathbf{v}_i $$
die Eigenvektoren der Matrix $$CV=X \cdot X^T$$ sind. D.h. es können zunächst die M Eigenvektoren der relativ kleinen Matrix $X^T \cdot X$ bestimmt und aus diesen durch eine einfache Multiplikation mit der Matrix $X$ die relevanten Eigenvektoren der Matrix $CV$ berechnet werden. Da die Matrix $X$ die $M$ Bildrepräsentationen $\Phi_i$ als Spalten enthält, können die gesuchten Eigenvektoren auch als Linearkombination der $M$ Bilder der Trainingsmenge beschrieben werden:
$$ \mathbf{u}_i=\sum_{k=1}^{M}{v_{i,k}\Phi_k} $$
wobei mit $v_{i,k}$ die $k.$te Komponente des Vektors $\mathbf{v}_i$ bezeichnet wird. Die Eigenvektoren $\mathbf{u}_i$ werden auch Eigenfaces genannt. Per Definition sind die Eigenvektoren paarweise orthogonal. Jeder Eigenvektor ist ein Spaltenvektor mit $Z$ (=Anzahl der Pixel) Komponenten.
Die $M$ Eigenvektoren werden dann entsprechend der Größe der zugehörigen Eigenwerte $\mu_i$ geordnet. Für die weiteren Schritte kann zum Zwecke einer weiteren Komplexitätsreduktion eine Untermenge der $K$ relevantesten Eigenvektoren benutzt werden (also der $K$ Eigenvektoren mit den höchsten Eigenwerten). Beispielsweise ist in M. Turk, A. Pentland; Eigenfaces for Recognition für die Erkennung von $M=16$ Personen und eine Auflösung von $256 \times 256$ Pixel meist $K=7$ Eigenvektoren für eine gute Erkennung ausgereicht.
Die $K$ ausgewählten Eigenvektoren $\mathbf{u}_1,\mathbf{u}_2,\ldots \mathbf{u}_K$ spannen einen $K-$dimensionalen Raum, den sogenannten Eigenspace auf. Die $K$ Vektoren repräsentieren die $K$ Merkmale hinsichtlich derer die Bilder der Trainingsdatenmenge am stärksten variieren.
Für die Bilderkennung wird jetzt jedes Bild, also sowohl die Bilder aus der Trainingsmenge als auch die zu erkennenden Bilder, in den Eigenspace transformiert. Jedes Bild stellt einen Punkt im Eigenspace dar. Für die Erkennung kann einfach die Distanz des zu erkennenden Bildes zu allen Bildern der Trainingsmenge berechnet werden. Das zu erkennende Bild wird der Person (Bildklasse) zugeordnet, deren Punkt im Eigenspace dem Punkt des zu erkennenden Bildes am nächsten liegt.
Die $K$ Komponenten eines Trainingsbildes werden berechnet, indem das Bild auf den jeweiligen Eigenvektor projiziert wird. Demnach ist die $k.$te Komponente des $i.$ten Trainingsbildes $\Phi_i$: $$ \omega_{k,i}=\mathbf{u}_k^T \Phi_i $$
Der dem Bild $\Phi_i$ entsprechende Punkt im Eigenspace ist dann $$ \mathbf{w}_i=[\omega_{1,i},\omega_{2,i},\ldots,\omega_{K,i}]. $$
Wird mit $\Gamma$ das zu erkennende Bild und mit $\Phi=\Gamma - \overline{\Gamma}$ die um den Mittelwert der Trainingsbilder subtrahierte Version des Bildes bezeichnet, dann sind $$ \omega_{k}=\mathbf{u}_k^T \Phi $$
die Koordinaten der Projektion von $\Phi$ in den Eigenspace und der dieses Bild repräsentierende Punkt $$ \mathbf{w}=[\omega_{1},\omega_{2},\ldots,\omega_{K}]. $$
Das zu erkennende Bild wird dann dem Trainingsbild $\Phi_j$ zugeordnet, für welches gilt: $$ j=argmin_{i} \left\{ d(\mathbf{w},\mathbf{w}_i) \right\} $$
wobei mit $d(\mathbf{w},\mathbf{w}_i)$ die euklidische Distanz zwischen den Projektionen von $\Phi$ und $\Phi_i$ bezeichnet wird.
Optional: Falls $\Phi_i$ nicht das einzige Bild einer Person in der Trainingsmenge ist, sondern für die entsprechende Person mehrere Trainingsbilder vorliegen, wird in der Distanzberechnung nicht $\Phi_i$, sondern der Mittelwert über alle zu dieser Person gehörenden Bilder eingesetzt:
$$ \overline{\Phi}=\frac{1}{|W|}\sum_{w \in W}^{}{\Phi_w} . $$
Dabei bezeichnet $W$ die Menge aller der Indizees $w$, für die die $\Phi_w$ zur gleichen Person gehören. Im Praktikumsversuch muss diese Option nicht implementiert werden. Die im folgenden Abschnitt beschriebene Versuchsdurchführung bezieht sich auf den Fall, dass nur die Distanz zu Einzelbildern berechnet wird und das nächstliegende Bild ausgegeben wird.
Für die Mindestdistanz $$ \epsilon =\min_{i} \left\{ d(\Phi,\Phi_i) \right\} $$
wird in der Regel eine Schwelle $T$ definiert. Wenn $\epsilon > T$ ist, also eine relativ große Distanz zwischen dem zu erkennenden Bild und dem nächstliegenden Bild aus der Trainingsmenge besteht, wird davon ausgegangen, dass es sich um ein unbekanntes Gesicht handelt. Optional könnte dieses unbekannte Gesicht in die Trainingsmenge aufgenommen werden.
Schließlich muss noch der Fall behandelt werden, dass das eingelesene Bild kein Gesicht darstellt. Aufgrund der starken Projektion vom ursprünglichen Bildraum in den Eigenspace kann dieser Fall nicht durch eine Schwelle auf den Fehler $\epsilon$ erkannt werden. Es kann durchaus sein, dass ein Nicht-Gesichtsbild in die Umgebung eines Gesichtsbild im Eigenspace projiziert wird. Ein Nicht-Gesichtsbild wird aber eine relativ große Distanz $d(\Phi,\Phi_f)$ zwischen
$$ \Phi=\Gamma - \overline{\Gamma} $$ und der Repräsentation im Eigenspace $$ \Phi_f=\sum_{k=1}^{K}{\omega_k}\mathbf{u}_k $$ aufweisen. Durch die Definition einer weiteren Schwelle $S$ auf $d(\Phi,\Phi_f)$ kann also erkannt werden, ob es sich überhaupt um ein Gesicht handelt. Im Versuch ist davon auszugehen, dass nur Gesichtsbilder verwendet werden, d.h. es muss nur der Test gegen die Schwelle $\epsilon$ implementiert werden.
Eigenvektor:
Ein Eigenvektor ($\vec{x}$ ) einer Matrix ist ein vom Nullvektor verschiedener Vektor, dessen Richtung durch Multiplikation mit der Matrix nicht verändert wird. Ein Eigenvektor wird also nur gestreckt.
Eigenwerte:
Der Streckungsfaktor (λ) heißt Eigenwert der Matrix.
Beispiel:
$\begin{pmatrix}3 & 0 \\-9 & 6\end{pmatrix}\cdot\begin{pmatrix}1 \\3\end{pmatrix}=\begin{pmatrix}3 \\ 9\end{pmatrix}$
Der Eigenvektor $\vec{x}$ der Matrix $A$ = $\begin{pmatrix}3 & 0 \\-9 & 6\end{pmatrix}$ ist $\begin{pmatrix}1 \\3\end{pmatrix}$, da eine Multiplikation von $\vec{x}$ mit $A$ nur eine Streckung von $\vec{x}$ um den Faktor 3 ergibt. Dieser Faktor 3 ist der Eigenwert der Matrix $A$.
Eigenfaces sind die resultierenden Eigenvektoren der Korrelationsmatrix, die alle Gesichter einer Trainingsmenge in serialisierter Form enthält. Die tatsächliche Gesichter können durch Linearkombinationen der Eigenfaces rekontruiert werden. Ist der Datensatz also groß genug, könnte man durch Kombination der Eigenfaces jedes menschliche Gesicht rekonstruieren. Verschiedene Eigenfaces encodieren dabei unterschiedliche Eigenschaften der Trainingsbilder, während ein Eigenface eher Teile der Frisur wiederspiegelt, repräsentieren andere eher die Lichtverhältnisse.
PCA:
Ein Bild wird in einen Block von $x_{1} \cdot x_{2}$ Pixel eingeteilt. Dadurch entsteht der (aus den $x_{1} \cdot x_{2}$ Pixeln) n-dimensionale Originalraum (Ortsbereich). Im transformierten Raum (Frequenzbereich) erhält man typischerweise hohe Werte im Bereich niedriger Frequenzen und niedrige Werte im Bereich hoher Frequenzen. Man sagt auch, dass die Information eines Blockes nahezu ausschließlich in den niedrigen Frequenzen liegt. Die PCA (Hauptkomponentenanalyse) bildet den Originalraum in einen transformierten Raum ab, in welchem die Varianz des Datensatzes ungleich über die neuen Dimensionen verteilt ist.
Durchführung der Transformation:
Mit dem Python Modul Image:
(verwenden wir für diesen Versuch nicht)
from PIL import Image
im = Image.open("example.jpg")
Mit dem Modul image vom matplotlib:
(in diesem Versuch von uns verwendet)
from matplotlib import image
im = image.imread("example.jpg")
Laden Sie vom Skripteserver das Verzeichnis Gesichtsbilder auf Ihren Rechner. Darin enthalten sind
Mit der unten gegebenen Funktion parseDirectory(directoryName,extension) wird eine Liste aller Dateinamen des Typs extension im Verzeichnis directoryName angelegt.
Aufgabe:
Alle Imports, die für diesen Versuch benötigt werden, sind hier zusammengefasst.
%matplotlib inline
import math
from os.path import isdir,join,normpath
from os import listdir
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import image as mplimg
import scipy.spatial.distance as ssd
import os.path
def parseDirectory(directoryName,extension):
'''This method returns a list of all filenames in the Directory directoryName.
For each file the complete absolute path is given in a normalized manner (with
double backslashes). Moreover only files with the specified extension are returned in
the list.
'''
if not isdir(directoryName): return
imagefilenameslist=sorted([
normpath(join(directoryName, fname))
for fname in listdir(directoryName)
if fname.lower().endswith('.'+extension)
])
return imagefilenameslist
curDir = os.path.dirname(os.getcwd()) + "/Resources/Gesichtsbilder/training/"
imagefilenamelist = parseDirectory(curDir, "png")
Einlesen aller png-Dateien des Verzeichnis training in ein numpy-Array. Jedes Bild, dessen Dateipfad in der Liste imagefilenamemlist steht, wird mit dem Modul image von matplotlib in ein zweidimensionales numpy-Array eingelesen, zu einem eindimensionalen numpy-Array serialisiert und alle Werte in den Bereich zwischen 0 und 1 normiert. Zurückgegeben wird die Höhe und Breite der Bilder und ein zweidimensionales numpy-Array, das alle Bilder in serialisierter und normierter Form enthält.
def readImageToNumpyData(imageList):
serializedImages = np.empty((0, 33000))
for image in imageList:
img = mplimg.imread(image)
height, width = img.shape
img.shape=(1, -1)
maxValue = np.amax(img)
img = img / maxValue
serializedImages = np.append(serializedImages, img, axis=0)
return serializedImages, width, height
imageList, imagewidth, imageheight = readImageToNumpyData(imagefilenamelist)
Aufgaben:
Die von der Funktion convertImgListToNumpyData(imgList) zurückgelieferte Matrix enthält in ihren Zeilen alle Trainingsbilder. Aus diesen Trainingsbildern ist nach der Gleichung für $\overline{\Gamma}$ das Durchschnittsbild zu berechnen, z.B. durch Anwendung der Numpy-Funktion average. Das Durchschnittsbild ist von allen Bildern abzuziehen (Gleichung $\Phi_i$). Das daraus resultierende Numpy-Array enthält die mittelwertfreien Repräsentationen der Trainingsbilder und wird im Folgenden mit NormedArrayOfFaces bezeichnet.
Zeigen Sie das Durchschnittsbild mithilfe der matplotlib.pyplot_-Funktion _imshow() an. Hierzu muss das eindimensionale Numpy Array, welches das Durchschnittsbild enthält, in ein zweidimensionales Array der ursprünglichen Bildgröße umgewandelt werden (Numpy Funktionen shape() oder reshape())
Wichtiger Hinweis: Das Numpy-Array NormedArrayOfFaces ist die Transpornierte $X^T$ der Matrix $X$ aus Gleichung $X$.
Berechnung des Durchschnittsbilds aller Trainingsbilder. Übergeben wird das zweidimensionale numpy-Array aller serialisierten, normierten Trainingsbilder. Zurückgegeben wird ein eindimensionales numpy-Array, das das Durchschnittsbild in serialisierter Form repräsentiert.
Berechnung der mittelwertfreien Repräsentationen der Trainingsbilder durch Abzug des Durchschnittsbilds von jedem Trainingsbilds.
Umwandlung des serialisierten Bilds in ein zweidimensionales numpy-Array der ursprünglichen Bildgröße und Ausgabe mithilfe des Moduls pyplot von matplotlib.
def calculateAverageImage(imageList):
return np.average(imageList, axis=0)
averageImage = calculateAverageImage(imageList)
def createNormedFacesArray(imageList, averageImage):
return imageList - averageImage
normedArrayOfFaces = createNormedFacesArray(imageList, averageImage)
def showImage(imageVector, imageheight, imagewidth):
img = np.reshape(imageVector, (imageheight, imagewidth))
plt.imshow(img, cmap='gray')
showImage(averageImage, imageheight, imagewidth)
Aufgaben:
Zunächst wird die Kovarianzmatrix CV der serialisierten, normierten, mitterwerfreien Trainingsbilder berechnet. Da die Komplexität bei Berechnung der Eigenwerte und Eigenvektoren der $X \cdot X^T$ Matrix (33000 Zeilen und 33000 Spalten) viel zu groß wäre, nutzen wir den von Turk beschriebenen Ansatz und berechnen uns die Kovarianzmatrix $X^T \cdot X$ (63 Zeilen und 63 Spalten). Auf Basis dieser Kovarianzmatrix werden die Eigenwerte und Eigenvektoren mithilfe der numpy-Funktion linalg.eigh berechnet. Durch eine Matrixmultiplikation der Eigenvektoren mit den serialisierten, normierten Trainingsbildern, erhalten wir die Eigenfaces. Die Eigenfaces werden anschließend absteigend nach zugehörigem Eigenwert sortiert und zurückgegeben.
def calculateEigenfaces(adjfaces):
CV = np.dot(adjfaces, adjfaces.T)
eigenvalues, eigenvectors = np.linalg.eigh(CV)
eigenfaces = np.dot(eigenvectors, adjfaces)
eigenvalues, eigenfaces = zip(*sorted(zip(eigenvalues, eigenfaces), reverse=True))
return np.array(eigenvalues), np.array(eigenfaces)
eigenvalues, eigenfaces = calculateEigenfaces(normedArrayOfFaces)
Aus der absteigend sortierten Liste der Eigenfaces werden nur die K wichtigsten zurückgegeben.
def returnRelevantEigenfaces(eigenfaces, K):
return eigenfaces[:K]
relevantEigenfaces = returnRelevantEigenfaces(eigenfaces, 6)
Wandelt die Eigenfaces in zweidimensionales numpy-Array der ursprünglichen Bildgröße um und gibt die Eigenfaces der übergebenen Liste aus.
def showEigenfaces(relevantEigenfaces):
fig=plt.figure(figsize=(8, 8))
columns = 3
rows = math.ceil(float(len(relevantEigenfaces) / float(columns)))
for i in range(1, len(relevantEigenfaces)+1):
img = np.reshape(relevantEigenfaces[i-1], (imageheight, imagewidth))
fig.add_subplot(rows, columns, i)
plt.imshow(img, cmap='gray')
plt.show()
showEigenfaces(relevantEigenfaces)
Trotz Dimensionsreduktion sind die Eigenfaces noch einigermaßen gut als menschliche Gesichter zu erkennen. Dabei legt jedoch jedes der 6 Eigenfaces bestimmte Schwerpunkte im Bezug auf die Merkmale (teilweise überschneiden sich diese natürlich auch, allerdings in unterschiedlicher Ausprägung). Bilder 2 und 6 fokussieren sich beide auf eine Brille sowie Haare, Bild 6 aber ausserdem auch besonders auf Bart und Kinnpartie. Bild 5 hingegen fokussiert sich überwiegend auf die Stirnpartie mit Haaren. Bei Bild 5 wird dabei bspw. ein Pony charakterisiert, während bei Bild 3 eine hohe Stirn mit entsprechend hohem Haaransatz charakterisiert wird.
Aufgabe:
Die im vorigen Schritt angelegten $K$ relevantesten Eigenfaces spannen den sogenannten Eigenface-Raum auf. Für jedes der normierten Trainingsbilder, also für jede Zeile aus NormedArrayOfFaces, sind die Koordinaten im Eigenface-Raum entsprechend der Gleichung für $\omega_{k,i}$ definierten Transformation zu berechnen.
eigenfaceCoords = np.dot(normedArrayOfFaces, relevantEigenfaces.T)
Aufgaben:
Es werden alle Bilder des Verzeichnis test eingelesen und mithilfe der Funktion readImageToNumpyData als zweidimensionalen numpy-Array in serialisierter, normierter Form zurückgegeben. Anschließend wird zufällig ein Bild der Testbilder ausgewählt. Dieses Bild, sowie die mittelwertfreie Version dieses Bilds, werden angezeigt.
testimagenamelist = parseDirectory(os.path.dirname(os.getcwd()) + "/Resources/Gesichtsbilder/test/", "png")
testimageList, imagewidth, imageheight = readImageToNumpyData(testimagenamelist)
fig=plt.figure(figsize=(8, 8))
randomtestimage = testimageList[np.random.randint(len(testimageList)-1)]
fig.add_subplot(1, 2, 1)
plt.imshow(np.reshape(randomtestimage, (imageheight, imagewidth)), cmap='gray')
normedTestFace = randomtestimage - averageImage
fig.add_subplot(1, 2, 2)
plt.imshow(np.reshape(normedTestFace, (imageheight, imagewidth)), cmap='gray')
plt.show()
Zunächst werden die Koordinaten des Testbilds im Eigenface-Raum berechnet. Dann wird die euklidische Distanz des Testbilds zu jedem Trainingsbild im Eigenface-Raum bestimmt. Der Index des Trainingsbilds mit der geringsten Distanz wird zurückgegeben.
def classifyTestImage(normedTestFace, relevantEigenfaces, eigenfaceCoords):
newNormedTestFace = np.dot(normedTestFace, relevantEigenfaces.T)
minDist = 99999
pos=-1
for i, trainface in enumerate(eigenfaceCoords):
distance = ssd.euclidean(newNormedTestFace, trainface)
if distance < minDist:
minDist = distance
pos = i
return pos
Durch die Funktion classifyTestImage lassen wir uns die Position des Trainingsbilds mit der geringsten euklidischen Distanz zum Testbild zurückgeben. Anschließend geben wir das Testbild und das gefundene Trainingsbild aus.
pos = classifyTestImage(normedTestFace, relevantEigenfaces, eigenfaceCoords)
fig=plt.figure(figsize=(8, 8))
fig.add_subplot(1, 2, 1)
plt.imshow(np.reshape(randomtestimage, (imageheight, imagewidth)), cmap='gray')
fig.add_subplot(1, 2, 2)
plt.imshow(np.reshape(imageList[pos], (imageheight, imagewidth)), cmap='gray')
plt.show()
Aufgaben:
Die Funktion bestimmt zu jedem Bild des Testverzeichnis das ähnlichste Bild der Trainingsbilder im Eigenface-Raum mithilfe der Funktion classifyTestImage und zeigt beide Bilder nebeneinander an.
def showFaceRecAll(testimageList, eigenfaceCoords, averageImage, relevantEigenfaces, imageList, imageheight, imagewidth):
fig=plt.figure(figsize=(8, 100))
rows = len(testimageList)
columns = 2
index = 1
for testimage in testimageList:
normedTestFace = testimage - averageImage
pos = classifyTestImage(normedTestFace, relevantEigenfaces, eigenfaceCoords)
fig.add_subplot(rows, columns, index)
plt.imshow(np.reshape(testimage, (imageheight, imagewidth)), cmap='gray')
index += 1
fig.add_subplot(rows, columns, index)
plt.imshow(np.reshape(imageList[pos], (imageheight, imagewidth)), cmap='gray')
index += 1
plt.show()
Fehlerrate = 4
Die vier fehlerhaft zugeordneten Trainingsbilder zeigen durchaus auch eine gewisse Ähnlichkeit zu den Testbildern auf. Wir vermuten, dass vor allem ein ähnlicher Bart oder ein ähnliches Lächeln zur fehlerhaften Zuordnung führt.
showFaceRecAll(testimageList, eigenfaceCoords, averageImage, relevantEigenfaces, imageList, imageheight, imagewidth)
Fehlerrate = 4
Wie zu erwarten, hat sich die Gesichtserkennung mit fünf Eigenfaces im Vergleich zu sechs Eigenfaces nicht verbessert. Es wurden die selben vier Gesichter falsch zugeordnet.
K = 5
relevantEigenfaces = returnRelevantEigenfaces(eigenfaces, K)
eigenfaceCoords = np.dot(normedArrayOfFaces, relevantEigenfaces.T)
showFaceRecAll(testimageList, eigenfaceCoords, averageImage, relevantEigenfaces, imageList, imageheight, imagewidth)
Fehlerrate = 4
Aauch mit zehn Eigenfaces hat sich die Zuordnung nicht zwingend verbessert. Zwar lassen sich etwas stärkere Ähnlichkeiten zwischen den falsch zugeordneten Trainingsbildern und den Testbildern erkennen, es werden jedoch weiterhin vier Gesichter falsch zugeordnet.
K = 10
relevantEigenfaces = returnRelevantEigenfaces(eigenfaces, K)
eigenfaceCoords = np.dot(normedArrayOfFaces, relevantEigenfaces.T)
showFaceRecAll(testimageList, eigenfaceCoords, averageImage, relevantEigenfaces, imageList, imageheight, imagewidth)
Fehlerrate = 3
Bei der Gesichterkennung mit 15 Eigenfaces hat sich die Fehlerrate auf drei reduziert. Jedoch ist es auffällig, dass das Testbild einer lachenden jungen Dame, die ihre Augen weit geöffnet hat und deren lange Haare ihre Stirn weitgehend bedecken, einem Trainingsbild eines bärtigen, jungen Manns zugeordnet wird, der eine eher ernste Miene, mit geschlossenem Mund und etwas zusammengekniffenen Augen, aufgegsetzt hat. Zwar haben beide lange Haare, jedoch ist die Stirn des jungen Manns nicht durch diese bedeckt. Die einzige Ähnlichkeit, die sich bei genauer Betrachtung beider Bilder feststellen lässt, sind einzelne Haare, die vom Kopf abstehen.
K = 15
relevantEigenfaces = returnRelevantEigenfaces(eigenfaces, K)
eigenfaceCoords = np.dot(normedArrayOfFaces, relevantEigenfaces.T)
showFaceRecAll(testimageList, eigenfaceCoords, averageImage, relevantEigenfaces, imageList, imageheight, imagewidth)
Fehlerrate = 2
Die Fehlerrate hat sich im Vergleich zu 15 Eigenface nochmal etwas verbessert. Nun werden nur noch zwei Bilder falsch zugeordnet. Es ist eigenartig, dass die oben beschriebene fehlerhafte Zuordnung auch hier wieder auftritt. Beim zweiten falsch zugeordneten Bild lassen sich durchaus Ähnlichkeiten zwischen den zwei jungen Männern feststellen.
K = 20
relevantEigenfaces = returnRelevantEigenfaces(eigenfaces, K)
eigenfaceCoords = np.dot(normedArrayOfFaces, relevantEigenfaces.T)
showFaceRecAll(testimageList, eigenfaceCoords, averageImage, relevantEigenfaces, imageList, imageheight, imagewidth)
Die implementierte Gesichtserkennung funktioniert schon relativ gut. Eine weitere Verbesserung könnte man erwarten, wenn farbige Bilder verwendet werden. Dann können Merkmale wie Hautfarbe, Augenfarbe und Haarfarbe gegebenenfalls besser verarbeitet werden. Es wäre auch spannend zu sehen, was passiert, wenn noch mehr Personen im Trainingsset enthalten wären. Den Fall, wenn mehrere verwandte Familienangehörige, z.B. Vater und Sohn, die sich ähnlich sehen, in den Bildern enthalten sind, schätzen wir für die Gesichtserkennung relativ schwierig ein. Bei solchen Fällen kann man wohl davon ausgehen, dass die Fehlerrate eher ansteigt.